package com.lry.util.requestcore;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lry.util.requestcore.domain.GRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.DigestUtils;

/**
 * 通用service
 *
 * @author ALI
 * @since 2023/5/10
 */
@Slf4j
public abstract class GRequestDealClient extends GSimpleRequest {

    private static final String LOG_PRE = "GRequestClient - ";

    /** 当前时间标识 */
    private static final String CURRENT_TIME = "currentTime";

    private static final String MY_ID = "my_id";
    private static final String TIME_STAMP_SUF = "_stamp";
    private static final String SPA = ",";
    private static final String DEFAULT_DATE_FIELD = "myCreateTime";
    private static final Pattern NUMBER_PATTERN = Pattern.compile("^\\d+$");
    /**
     * 实际的id
     */
    protected String id;
    /**
     * 请求头
     */
    private Map<String, String> loginHeader = new HashMap<>();
    /**
     * 是否进行登录
     */
    private boolean login;

    /**
     * 获取登录完成的header信息
     *
     * @return 新的map对象
     */
    public Map<String, String> getLoginHeader() {
        return new HashMap<>(loginHeader);
    }

    public void request(GRequest request) {
        checkParam(request);
        populateFilterParam(request);
        String url = handlerUrl(request.getApi());
        request.setApi(url);
        GRequest.RequestParam requestParam = request.getRequestParam();
        GRequest.Response response = request.getResponse();
        log("请求地址：【{}】{}", request.getMethod(), url);
        log("请求参数：{}", requestParam.getParam());
        log("请求体：{}", requestParam.getBody());
        Map<String, String> headers = completeHeader(request);
        log("请求头信息：{}", headers);
        String body = doRequest(request);
        if (StringUtils.isBlank(body)) {
            log("没有拿到数据！url：{}", url);
            return;
        }
        JSONArray saveList = getSaveList(request, body);
        if (saveList == null || saveList.isEmpty()) {
            log("没有数据，退出！");
            return;
        }
        log("数据量：{}条", saveList.size());
        filterField(saveList, response.getSaveFields());
        populateId(request, saveList);
        List<Map<String, Object>> maps = dataPostProcessor(request, saveList);
        initTable(request, maps);
        saveToDb(request, saveList);
        if (request.getPageInfo() != null) {
            request.getPageInfo().setPageNum(request.getPageInfo().getPageNum() + 1);
            request.getRequestParam().setOffsetDateField("null");
            request(request);
        }
        log("数据转存完成！！！");
    }

    @Override
    public String getToken(String url) {
        return TokenCache.get(url);
    }

    @Override
    public void setToken(String url, String token) {
        if (StringUtils.isBlank(url)) {
            url = this.toString();
        }
        TokenCache.add(url, token);
    }

    protected boolean isLogin(HttpResponse httpResponse) {
        return httpResponse.getStatus() == 401 || httpResponse.getStatus() == 302;
    }

    public abstract JdbcTemplate getJdbcTemplate();

    @Override
    protected Map<String, String> getHeader(String token) {
        return null;
    }

    @Override
    protected String login() {
        return null;
    }

    public String login(GRequest request) {
        GRequest.LoginRequest loginRequest = request.getLoginRequest();
        if (loginRequest == null) {
            return null;
        }
        HttpResponse httpResponse = MyHttpUtil.post(loginRequest.getUrl(), loginRequest.getBody());
        if (httpResponse == null) {
            throw new RuntimeException(LOG_PRE + "登录失败！");
        }
        String res = httpResponse.body();
        JSONObject jsonObject = JSON.parseObject(res);
        String tokenField = loginRequest.getTokenFieldName();
        String[] split = tokenField.split("\\.");
        if (split.length == 1) {
            token = jsonObject.getString(split[0]);
        } else {
            JSONObject temp = null;
            for (int i = 0; i < split.length - 1; i++) {
                temp = jsonObject.getJSONObject(split[i]);
            }
            token = temp.getString(split[split.length - 1]);
        }
        if (StringUtils.isBlank(token)) {
            throw new RuntimeException(LOG_PRE + "登录失败");
        }
        return String.format(loginRequest.getTokenFormat(), token);
    }

    private static void checkParam(GRequest request) {
        if (request == null) {
            throw new RuntimeException("任务参数不能为空");
        }
        if (StringUtils.isBlank(request.getApi())) {
            throw new RuntimeException("请求api不能为空");
        }
        if (StringUtils.isBlank(request.getTargetTable())) {
            throw new RuntimeException("未指定存储表");
        }
    }

    private static void putParamPage(GRequest request) {
        boolean setPage = false;
        GRequest.RequestParam requestParam = request.getRequestParam();
        GRequest.PageReq pageInfo = request.getPageInfo();
        if (requestParam.getParam() != null) {
            for (Map.Entry<String, Object> entry : requestParam.getParam().entrySet()) {
                if (entry.getKey().equals(pageInfo.getPageNumParam())) {
                    entry.setValue(pageInfo.getPageNum());
                    setPage = true;
                } else if (entry.getKey().equals(pageInfo.getPageSizeParam())) {
                    entry.setValue(pageInfo.getPageSize());
                }
            }
            if (!setPage) {
                requestParam.getParam().putAll(pageInfo.getPageParam());
            }
        }
    }

    private static void putBodyPage(GRequest request) {
        boolean setPage = false;
        GRequest.RequestParam requestParam = request.getRequestParam();
        GRequest.PageReq pageInfo = request.getPageInfo();
        if (requestParam.getBody() != null) {
            for (Map.Entry<String, Object> entry : requestParam.getBody().entrySet()) {
                if (entry.getKey().equals(pageInfo.getPageNumParam())) {
                    entry.setValue(pageInfo.getPageNum());
                    setPage = true;
                } else if (entry.getKey().equals(pageInfo.getPageSizeParam())) {
                    entry.setValue(pageInfo.getPageSize());
                }
            }
            if (!setPage) {
                requestParam.getBody().putAll(pageInfo.getPageParam());
            }
        }
    }

    private static void filterField(JSONArray saveList, List<String> saveFields) {
        if (CollectionUtils.isEmpty(saveFields)) {
            return;
        }
        for (Object o : saveList) {
            JSONObject map = (JSONObject) o;
            map.entrySet().removeIf(next -> !saveFields.contains(next.getKey()));
        }
    }

    private static String generateId(Map<String, Object> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            sb.append(entry.getKey().trim()).append(String.valueOf(entry.getValue()).trim());
        }
        return DigestUtils.md5DigestAsHex(sb.toString().getBytes());
    }

    private Map<String, String> parseDateFormat(GRequest.DateFieldFormat dateFieldFormat, List<String> fields) {
        String format = dateFieldFormat.getFormat();
        Map<String, String> df = new HashMap<>();
        for (String field : fields) {
            if (field.contains(SPA)) {
                String[] split = field.split(SPA);
                df.put(split[0].trim(), split[1].trim());
            } else {
                df.put(field, format);
            }
        }
        return df;
    }

    /**
     * 填充过滤参数
     *
     * @param request 参数对象
     */
    private void populateFilterParam(GRequest request) {
        GRequest.RequestParam requestParam = request.getRequestParam();
        // 构建分页参数
        populatePageParam(request);
        // 待构建的参数对象
        Map<String, String> buildParam = new HashMap<>();
        putOffsetStarTime(request, buildParam);
        putOffsetEndTime(request, buildParam);
        populateParam(requestParam, buildParam);
    }

    /**
     * 填充分页参数
     *
     * @param request 实际的参数对象
     */
    private void populatePageParam(GRequest request) {
        GRequest.RequestParam requestParam = request.getRequestParam();
        if (request.getPageInfo() != null) {
            if (requestParam.getBody() != null && !requestParam.getBody().isEmpty()) {
                putBodyPage(request);
            }
            if (requestParam.getParam() != null && !requestParam.getParam().isEmpty()) {
                putParamPage(request);
            }
        }
    }

    /**
     * 构建偏移结束时间
     */
    private void putOffsetEndTime(GRequest request, Map<String, String> buildParam) {
        GRequest.RequestParam requestParam = request.getRequestParam();
        String endDateField = requestParam.getEndDateField();
        String endDate = "";
        if (StringUtils.isNotBlank(endDateField)) {
            String ev = requestParam.getEndDateFieldValue();
            if (CURRENT_TIME.equals(ev)) {
                endDate = DateUtil.format(new Date(), requestParam.getDateFormat());
                buildParam.put(endDateField, endDate);
            }
        }
    }

    private void populateId(GRequest request, JSONArray saveList) {
        if (StringUtils.isNotBlank(request.getResponse().getIdField())) {
            id = request.getResponse().getIdField();
            return;
        }
        for (Object o : saveList) {
            JSONObject map = (JSONObject) o;
            map.put(MY_ID, generateId(map));

        }
    }

    private Map<String, String> mappingLoginHeader(GRequest request, String token) {
        Map<String, String> header = new HashMap<>();
        GRequest.LoginRequest loginRequest = request.getLoginRequest();
        if (loginRequest == null) {
            return header;
        }
        String headerMapping = loginRequest.getHeaderMapping();
        if (StringUtils.isBlank(token)) {
            return header;
        }
        header.put(headerMapping, token);
        return header;
    }

    private Map<String, String> completeHeader(GRequest request) {
        Map<String, String> result = new HashMap<>();
        if (request.getHeaders() != null && (request.getHeaders() != null && !request.getHeaders().isEmpty())) {
            result.putAll(request.getHeaders());
        }
        if (!result.containsKey("Content-Type")) {
            result.put("Content-Type", "application/json");
        }
        postProcessorAfterComplete(result);
        request.setHeaders(result);
        return result;
    }

    private void initTable(GRequest request, List<Map<String, Object>> saveList) {
        if (request.getCreatedTable()) {
            return;
        }
        String idField = request.getResponse().getIdField();
        if (StringUtils.isBlank(idField)) {
            id = idField = MY_ID;
            request.getResponse().setIdField(idField);
        }
        initTable(request.getTargetTable(), request.getResponse().getIdField(), saveList);
        if (saveList.isEmpty()) {
            return;
        }
        request.setCreatedTable(true);
    }

    private void saveToDb(GRequest request, JSONArray saveList) {
        if (saveList.isEmpty()) {
            return;
        }
        execute(request.getTargetTable(), request.getResponse().getIdField(), saveList);
    }

    private void check(HttpResponse httpResponse) {
        if (httpResponse == null || httpResponse.getStatus() >= 500) {
            throw new RuntimeException(LOG_PRE + "远程服务异常！" + httpResponse.body());
        }
        if (401 == httpResponse.getStatus()) {
            throw new RuntimeException("认证失败！" + httpResponse.body());
        }
        if (400 < httpResponse.getStatus() && httpResponse.getStatus() < 500) {
            throw new RuntimeException(LOG_PRE + "参数或认证失败！" + httpResponse.body());
        }
    }

    private Map<String, String> getHeaders(GRequest request) {
        Map<String, String> headers = request.getHeaders();
        Map<String, String> tokenHeader = mappingLoginHeader(request, getToken(request.getApi()));
        headers.putAll(tokenHeader);
        return headers;
    }

    private List<Map<String, Object>> dataPostProcessor(GRequest request, JSONArray saveList) {
        return saveList.stream().map(d -> (JSONObject) d).filter(this::filter).peek(d -> consumer(request, d))
                       .collect(Collectors.toList());
    }

    /**
     * 参数填充
     *
     * @param requestParam 实际的参数对象
     * @param buildParam   已构建完成的参数
     */
    private void populateParam(GRequest.RequestParam requestParam, Map<String, String> buildParam) {
        Map<String, Object> paramSource = requestParam.getParam();
        Map<String, Object> bodySource = requestParam.getBody();
        for (Map.Entry<String, String> entry : buildParam.entrySet()) {
            if (paramSource != null && paramSource.containsKey(entry.getKey())) {
                paramSource.put(entry.getKey(), entry.getValue());
            }
            if (bodySource != null && bodySource.containsKey(entry.getKey())) {
                bodySource.put(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 构建偏移开始时间
     */
    private void putOffsetStarTime(GRequest request, Map<String, String> buildParam) {
        GRequest.RequestParam requestParam = request.getRequestParam();
        String offsetDateField = requestParam.getOffsetDateField();
        if (StringUtils.isNotBlank(offsetDateField)) {
            DateTime dateTime = null;
            if (requestParam.getOffsetDate() != null) {
                DateField dateField = DateField.MONTH;
                if ("s".equals(requestParam.getOffsetDateUnit())) {
                    dateField = DateField.SECOND;
                } else if ("m".equals(requestParam.getOffsetDateUnit())) {
                    dateField = DateField.MINUTE;
                } else if ("h".equals(requestParam.getOffsetDateUnit())) {
                    dateField = DateField.HOUR_OF_DAY;
                } else if ("d".equals(requestParam.getOffsetDateUnit())) {
                    dateField = DateField.DAY_OF_MONTH;
                }
                dateTime = DateUtil.offset(new Date(), dateField, requestParam.getOffsetDate());
            } else {
                dateTime = getLastTime(request.getTargetTable(), requestParam.getDbOffsetDateField(),
                                       requestParam.getDateFormat());
            }
            if (dateTime != null) {
                if (requestParam.getOffsetDateString()) {
                    buildParam.put(requestParam.getOffsetDateField(), String.format(requestParam.getOffsetDateFormat(),
                                                                                    DateUtil.format(dateTime,
                                                                                                    GRequest.DATE_FORMAT)));
                } else {
                    buildParam.put(requestParam.getOffsetDateField(),
                                   String.format(requestParam.getOffsetDateFormat(), dateTime.getTime()));
                }
            }
        }
    }

    private DateTime getLastTime(String tableName, String dateFiled, String dateFormat) {
        String sql = "SELECT %s FROM %s ORDER BY %s DESC LIMIT 1;";
        try {
            Map<String, Object> map = getJdbcTemplate().queryForMap(
                String.format(sql, dateFiled, tableName, dateFiled));
            if (map.isEmpty()) {
                return null;
            } else {
                Object date = map.get(dateFiled);
                if (date == null) {
                    return null;
                }
                if (date instanceof Date) {
                    return new DateTime((Date) date);
                } else if (date instanceof String) {
                    if (StringUtils.isBlank(date.toString())) {
                        return null;
                    }

                    Matcher matcher = NUMBER_PATTERN.matcher(date.toString());
                    if (matcher.matches()) {
                        return new DateTime(Long.parseLong(date.toString()));
                    }
                    return DateUtil.parse(date.toString(), dateFormat);
                } else if (date instanceof Number) {
                    return new DateTime((Long) date);
                } else {
                    throw new RuntimeException("未知的时间格式！！！！！！！！");
                }
            }
        } catch (DataAccessException e) {

        }
        return null;
    }

    protected boolean filter(JSONObject json) {
        return true;
    }

    protected void consumer(GRequest request, JSONObject json) {
        GRequest.DateFieldFormat dateFieldFormat = request.getResponse().getDateFieldFormat();
        if (dateFieldFormat == null) {
            return;
        }
        List<String> fields = dateFieldFormat.getFields();
        if (fields.isEmpty()) {
            return;
        }
        Map<String, String> df = parseDateFormat(dateFieldFormat, fields);

        String suf = request.getResponse().getDateFieldFormat().getSuf();
        if (StringUtils.isBlank(suf)) {
            suf = TIME_STAMP_SUF;
        }
        Map<String, Long> time = new HashMap<>();
        for (Map.Entry<String, Object> entry : json.entrySet()) {
            if (!df.containsKey(entry.getKey())) {
                continue;
            }
            if (entry.getValue() == null || StringUtils.isBlank(entry.getValue().toString())) {
                continue;
            }
            Long timestamp = null;
            if (entry.getValue() instanceof Date) {
                timestamp = ((Date) entry.getValue()).getTime();
            } else {
                try {
                    timestamp = DateUtil.parse(entry.getValue().toString(), df.get(entry.getKey())).getTime();
                } catch (Exception e) {
                    log("时间戳解析失败！！！--->>>值：", entry.getValue());
                    continue;
                }
            }
            time.put(entry.getKey() + suf, timestamp);
        }
        if (!time.isEmpty()) {
            json.putAll(time);
        }
        if (request.getAddDefaultField()) {
            Map<String, Object> defaultFields = new HashMap<>();
            defaultFields.put(DEFAULT_DATE_FIELD, new Date());
            defaultFields.put(DEFAULT_DATE_FIELD + suf, new Date().getTime());
            json.putAll(defaultFields);
        }
    }

    protected String handlerUrl(String url) {
        return url;
    }

    protected abstract JSONArray getSaveList(GRequest request, String body);

    protected abstract void postProcessorAfterComplete(Map<String, String> headers);

    protected String doRequest(GRequest request) {
        HttpResponse response = null;
        GRequest.RequestParam requestParam = request.getRequestParam();
        response = MyHttpUtil.request(request.getMethod(), request.getApi(), getHeaders(request),
                                      requestParam.getParam(), requestParam.getBody());
        if (isLogin(response) && !login) {
            log("认证失败！");
            GRequest.LoginRequest loginRequest = request.getLoginRequest();
            if (loginRequest != null && StringUtils.isNotBlank(loginRequest.getUrl())) {
                log("进行默认登录...");
                log("登录地址：【{}】", loginRequest.getUrl());
                String token = null;
                try {
                    token = login(request);
                    if (StringUtils.isNotBlank(token)) {
                        setToken(request.getApi(), token);
                        log("认证token：{}", token);
                    }
                } catch (Exception e) {
                    throw e;
                } finally {
                    login = true;
                }
                loginHeader = mappingLoginHeader(request, token);
                request.getHeaders().putAll(loginHeader);
            }
            response = MyHttpUtil.request(request.getMethod(), request.getApi(), getHeaders(request),
                                          requestParam.getParam(), requestParam.getBody());
            check(response);
        }
        return response.body();
    }
}
